package jasepro;

import java.io.BufferedWriter;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.net.Socket;
import java.net.URL;
import java.net.UnknownHostException;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;
import java.util.StringTokenizer;

import javax.net.ssl.HandshakeCompletedEvent;
import javax.net.ssl.HandshakeCompletedListener;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;

public class ProxyThread extends Thread {
    Socket clientSocket = null;
    Socket serverSocket = null;
    String localHostName = null;

    ProxyThread(Socket clientSocket, String localHostName) {
        this.clientSocket = clientSocket;
        this.localHostName = localHostName;
    }

    public void run() {
        HttpRequestHdr request = new HttpRequestHdr();
        HttpReplyHdr response = new HttpReplyHdr();

        try {
            request.parse(clientSocket.getInputStream());
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        String urlToServer = request.url;

        if (request.getMethod().equals("CONNECT")) {
            try {
                setupTunnel("http://" + urlToServer);
            } catch (UnknownHostException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        } else {
            processHttpRequest(urlToServer, request, response);
        }
    }

    /*private void processHttpsRequest() {
        try {
            setupTunnel();
            SSLServerSocketFactory ssf = (SSLServerSocketFactory) SSLServerSocketFactory.getDefault();
            ServerSocket ss = ssf.createServerSocket(443);
            System.out.println("Init server socket successfully!");
            while (true) {
                Socket s = ss.accept();
                PrintStream out = new PrintStream(s.getOutputStream());
                BufferedReader in = new BufferedReader(new InputStreamReader(s.getInputStream()));
                String info = null;
                String request = null;
                String refer = null;

                while ((info = in.readLine()) != null) {
                    if (info.startsWith("GET")) {
                        request = info;
                    }
                    if (info.startsWith("Referer:")) {
                        refer = info;
                    }
                    if (info.equals("")) break;
                }
                if (request != null) {
                    out.println("HTTP/1.0 200 OK\nMIME_version:1.0\nContent_Type:text/html");
                    int sp1 = request.indexOf(' ');
                    int sp2 = request.indexOf(' ', sp1 + 1);
                    String filename = request.substring(sp1 + 2, sp2);
                    if (refer != null) {
                        sp1 = refer.indexOf(' ');
                        refer = refer.substring(sp1 + 1, refer.length());
                        if (!refer.endsWith("/")) {
                            refer = refer + "/";
                        }
                        filename = refer + filename;
                    }
                    URL con = new URL(filename);
                    InputStream gotoin = con.openStream();
                    int n = gotoin.available();
                    byte buf[] = new byte[1024];
                    out.println("HTTP/1.0 200 OK\nMIME_version:1.0\nContent_Type:text/html");
                    out.println("Content_Length:" + n + "\n");
                    while ((n = gotoin.read(buf)) >= 0) {
                        out.write(buf, 0, n);
                    }
                    out.close();
                    s.close();
                    in.close();
                }
            }

        } catch (Exception e) {
            System.out.println(e);
        }
    }*/

    private void setupTunnel(String url) throws UnknownHostException, IOException {
        SSLSocketFactory factory = (SSLSocketFactory) SSLSocketFactory.getDefault();
        /*
         * Set up a socket to do tunneling through the proxy. Start it off as a regular socket, then layer SSL over the
         * top of it.
         */
        String tunnelHost = /*System.getProperty("https.proxyHost");*/ "ChauDinh";
        int tunnelPort = /*Integer.getInteger("https.proxyPort").intValue()*/8080;

        Socket tunnel = new Socket(tunnelHost, tunnelPort);
        doTunnelHandshake(tunnel, "www.google.com.vn", 443);
        SSLSocket socket = (SSLSocket) factory.createSocket(tunnel, tunnelHost, tunnelPort, true);
        /*
         * register a callback for handshaking completion event
         */
        socket.addHandshakeCompletedListener(new HandshakeCompletedListener() {
            public void handshakeCompleted(HandshakeCompletedEvent event) {
                System.out.println("Handshake finished!");
                System.out.println("\t CipherSuite:" + event.getCipherSuite());
                System.out.println("\t SessionId " + event.getSession());
                System.out.println("\t PeerHost " + event.getSession().getPeerHost());
            }
        });
        /*
         * send http request
         * 
         * See SSLSocketClient.java for more information about why there is a forced handshake here when using
         * PrintWriters.
         */
        socket.startHandshake();

        PrintWriter out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())));

        out.println("GET http://www.verisign.com/index.html HTTP/1.0");
        out.println();
        out.flush();
    }

    private void doTunnelHandshake(Socket tunnel, String tunnelHost, int tunnelPort) throws IOException {
        OutputStream out = tunnel.getOutputStream();
        String msg = "CONNECT " + tunnelHost + ":" + tunnelPort + " HTTP/1.0\n" + "User-Agent: "
                + sun.net.www.protocol.http.HttpURLConnection.userAgent + "\r\n\r\n";
        byte b[];
        try {
            /*
             * We really do want ASCII7 -- the http protocol doesn't change with locale.
             */
            b = msg.getBytes("ASCII7");
        } catch (UnsupportedEncodingException ignored) {
            /*
             * If ASCII7 isn't there, something serious is wrong, but Paranoia Is Good (tm)
             */
            b = msg.getBytes();
        }
        out.write(b);
        out.flush();

        /*
         * We need to store the reply so we can create a detailed error message to the user.
         */
        byte reply[] = new byte[200];
        int replyLen = 0;
        int newlinesSeen = 0;
        boolean headerDone = false; /* Done on first newline */

        InputStream in = tunnel.getInputStream();
        boolean error = false;

        while (newlinesSeen < 2) {
            int i = in.read();
            if (i < 0) {
                throw new IOException("Unexpected EOF from proxy");
            }
            if (i == '\n') {
                headerDone = true;
                ++newlinesSeen;
            } else if (i != '\r') {
                newlinesSeen = 0;
                if (!headerDone && replyLen < reply.length) {
                    reply[replyLen++] = (byte) i;
                }
            }
        }

        /*
         * Converting the byte array to a string is slightly wasteful in the case where the connection was successful,
         * but it's insignificant compared to the network overhead.
         */
        String replyStr;
        try {
            replyStr = new String(reply, 0, replyLen, "ASCII7");
        } catch (UnsupportedEncodingException ignored) {
            replyStr = new String(reply, 0, replyLen);
        }

        /*
         * We check for Connection Established because our proxy returns HTTP/1.1 instead of 1.0
         */
        // if (!replyStr.startsWith("HTTP/1.0 200")) {
        if (replyStr.toLowerCase().indexOf("200 connection established") == -1) {
            throw new IOException("Unable to tunnel through " + tunnelHost + ":" + tunnelPort + ".  Proxy returns \""
                    + replyStr + "\"");
        }

        /* tunneling Handshake was successful! */

    }

    private void processHttpRequest(String urlToServer, HttpRequestHdr request, HttpReplyHdr response) {
        try {
            URL url = new URL(urlToServer);
            System.out.println("Request = " + url);

            serverSocket = new Socket(serverName(urlToServer), serverPort(urlToServer));

            request.url = serverUrl(urlToServer);

            DataOutputStream serverOut = new DataOutputStream(serverSocket.getOutputStream());

            serverOut.writeBytes(request.toString());
            serverOut.flush();

            for (int i = 0; i < request.contentLength; i++) {
                serverSocket.getOutputStream().write(clientSocket.getInputStream().read());
            }
            serverSocket.getOutputStream().flush();

            DataInputStream Din = new DataInputStream(serverSocket.getInputStream());
            DataOutputStream Dout = new DataOutputStream(clientSocket.getOutputStream());
            String str = Din.readLine();
            StringTokenizer s = new StringTokenizer(str);
            String retCode = s.nextToken();
            retCode = s.nextToken();

            String tempStr = new String(str + "\r\n");
            Dout.writeBytes(tempStr);
            if (str.length() > 0) while (true) {
                str = Din.readLine();
                tempStr = new String(str + "\r\n");

                // Send bits to client
                Dout.writeBytes(tempStr);
                if (str.length() <= 0) break;
            }
            Dout.flush();

            InputStream in = serverSocket.getInputStream();
            OutputStream out = clientSocket.getOutputStream();

            byte data[] = new byte[2000];
            int count;
            while ((count = in.read(data)) > 0) {
                out.write(data, 0, count);
            }
            out.flush();
        } catch (UnknownHostException uhe) {
            System.out.println("Server Not Found.");

            try {
                // chuyen thong bao cho client
                DataOutputStream out = new DataOutputStream(clientSocket.getOutputStream());
                out.writeBytes(response.formServerNotFound());
                out.flush();
            } catch (Exception uhe2) {
            }
        } catch (Exception e) {
            System.out.println(e);
            try {
                DataOutputStream out = new DataOutputStream(clientSocket.getOutputStream());
                out.writeBytes(response.formTimeout());
                out.flush();

            } catch (Exception uhe2) {
            }
        } finally {
            try {
                clientSocket.getOutputStream().flush();
                clientSocket.close();
            } catch (Exception e) {
                System.out.println(e.getMessage());
            }
        }
    }

    private String serverName(String str) {
        // chop to "server.name:x/thing"
        int i = str.indexOf("//");
        if (i < 0) return "";
        str = str.substring(i + 2);

        // chop to server.name:xx
        i = str.indexOf("/");
        if (0 < i) str = str.substring(0, i);

        // chop to server.name
        i = str.indexOf(":");
        if (0 < i) str = str.substring(0, i);

        return str;
    }

    private int serverPort(String str) {
        // chop to "server.name:x/thing"
        int i = str.indexOf("//");
        if (i < 0) return 80;
        str = str.substring(i + 2);

        // chop to server.name:xx
        i = str.indexOf("/");
        if (0 < i) str = str.substring(0, i);

        // chop XX
        i = str.indexOf(":");

        return 0 < i ? Integer.parseInt(str.substring(i).trim()) : 80;
    }

    private String serverUrl(String str) {
        int i = str.indexOf("//");
        if (i < 0) return str;

        str = str.substring(i + 2);
        i = str.indexOf("/");
        if (i < 0) return str;

        return str.substring(i);
    }

    private void disableCertificateValidation() {
        // Create a trust manager that does not validate certificate chains
        TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() {
            public X509Certificate[] getAcceptedIssuers() {
                return new X509Certificate[0];
            }

            public void checkClientTrusted(X509Certificate[] certs, String authType) {
            }

            public void checkServerTrusted(X509Certificate[] certs, String authType) {
            }
        } };

        // Ignore differences between given hostname and certificate hostname
        HostnameVerifier hv = new HostnameVerifier() {
            public boolean verify(String hostname, SSLSession session) {
                return true;
            }
        };

        // Install the all-trusting trust manager
        try {
            SSLContext sc = SSLContext.getInstance("SSL");
            sc.init(null, trustAllCerts, new SecureRandom());
            HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
            HttpsURLConnection.setDefaultHostnameVerifier(hv);
        } catch (Exception e) {
        }
    }

}
